<?
/**
 * Gravy Application Class.
 *
 * This class is the main instantiation of the Gravy Framework. This class loads all classes and manages all operations for a given website.
 *
 * @author Lars Sorhus (lars@saucecode.com.au)
 * @internal It's not recommended at all to modify this code.
 * @package application
 */
class application extends gravy{
        /**
         * <b>Theme</b>
         *
         * Variable holds the theme (template) that will be sent to the browser.
         *
         * @var string
         */
	public $theme;

        /**
         * <b>View</b>
         *
         * Variable holds the view (page) that will be included in the theme (template) and sent to the browser.
         *
         * @var string
         */
        public $view;

        /**
         * <b>Controller</b>
         *
         * Variable holds the Controller (tasks associated with the view) that will be run before anything is published to the browser.
         *
         * @var string
         */
	public $controller;

        /**
         * <b>Error Theme</b>
         *
         * Variable holds the Error Theme to use when/if an error occurs.
         *
         * @var string
         */
	public $error_theme;

        /**
         * <b>Output Format</b>
         *
         * Variable holds the output format of the content that will be pubished. Some options include: html (default), xml, json, word, excel.
         *
         * @var string
         */
	public $format="html";

        /**
         * <b>Data Container</b>
         *
         * This variable hold an array of all data that will be available to the application.
         *
         * @var array
         */
	public $data;

        /**
         * <b>JavaScript Files Container</b>
         *
         * This variable hold an array of all JavaScript files that should be included in the HEAD of the theme (template).
         *
         * @var array
         */
	public $js_files;

        /**
         * <b>CSS Files Container</b>
         *
         * This variable hold an array of all CSS files that should be included in the HEAD of the theme (template).
         *
         * @var array
         */
	public $css_files;

	public $allowXml=false;
	
	public $xml_data;

	public $allowJson=false;
	
	public $json_data;

	public $renderer;

	public $pager;
	
	//The page's title.
	public $pageTitle="";
	
	//$_REQUEST wrapper (Abbr for Request Variables).
	public $rv=NULL;

	function __construct(){
		parent::__construct();
		
		$this->data=array();
		
		$this->xml_data=array();
		
		$this->json_data=array();
		
		$this->js_files=array();
		
		$this->css_files=array();

		#Setup the Format of the document.
		$this->format=(isset($_REQUEST["format"]))?$this->sanitize($_REQUEST["format"]):$this->config->default_format;
		$this->format=($this->format=="php")?"html":$this->format;
		
		#Load in the request variables.
		$this->setRequestData();
		
		#Load in the Theme, view and Controller Name from either the URL or the config file.
		$this->loadTvcDetails();

		#Set the page to the default page title, for the moment.
		$this->pageTitle=$this->config->default_page_title;
		
		$this->logAdmin();
		
		$this->phpInfo();
	}

	/*
	** Adds a variable to the rv (Request Variable). If rv isnt setup yet it also sets that up.
	**
	**  @return void
	*/
	public function setRequestData($name="",$value=null){
		$special=array('format','PHPSESSID');
		if($this->rv==NULL){
			$this->rv=array();
			foreach($_REQUEST as $key=>$val){
				if(!in_array($key,$special)){
					if(is_array($val)){
						for($c=0;$c<count($val);$c++){
							$val[$c]=stripslashes($val[$c]);
						}
						if(array_key_exists($key,$this->rv)){
							$this->rv[$key]=array_merge($this->rv[$key],$val);
						}else{
							$this->rv[$key]=$val;
						}
					}else{
						if(array_key_exists($key,$this->rv)){
							if(is_array($this->rv[$key])){
								$this->rv[$key][]=$val;
							}else{
								$tmp=$this->rv[$key];
								$this->rv[$key]=array($tmp);
							}
						}else{
							$this->rv[$key]=stripslashes($val);
						}
					}
				}
			}
		}
		if(!in_array($name,$special)&&strlen($name)>0&&$value!=null){
			$this->rv[$name]=$value;
		}
	}
	
	/*
	** Loads in the Theme, view and controller to use.
	**
	**  This information by default is specified in the Config File, but can be manipulated/changed in the URL.
	**  The URL format is like so server.com/theme/path,to,view/controller/idToPass-anotherIdToPass-yetAnotherIdToPass
	**
	**  @return void
	*/
	public function loadTvcDetails(){
		$uri=(strlen($_REQUEST["custom_uri"])>0)?$_REQUEST["custom_uri"]:$_SERVER['REQUEST_URI'];
		
		$tvc=preg_replace("/".str_replace("/","\\/",WEB_ROOT)."/","",$uri,1);
		
		$tvcs=@split("/",$tvc);
		
		#$this->debug("tvc",$tvc);
		$tvcs=@split("\?",$tvc);
		#$this->debug("tvcs",$tvcs);
		$tvcs=@split("/",$tvcs[0]);
		
		#$this->debug("tvcs",$tvcs);
		
		$theme=$view=$controller=$qs="";
		
		$c_index=-1;
		
		for($c=0;$c<count($tvcs);$c++){
			switch($c){
				case 0:
					if(file_exists(THEMES_ROOT_DIR.$tvcs[$c].".".TEMPLATE_EXT)&&strlen($theme)<1){
						$theme=$tvcs[$c];
						$c_index=$c;
					}elseif(file_exists(VIEWS_ROOT_DIR.$tvcs[$c])&&strlen($view)<1){
						$view=$tvcs[$c];
						$c_index=$c;
					}elseif(file_exists(VIEWS_ROOT_DIR.$view."/".$tvcs[$c].".".TEMPLATE_EXT)&&strlen($controller)<1){
						$controller=$tvcs[$c];
						$c_index=$c;
					}elseif(file_exists(VIEWS_ROOT_DIR.$this->config->default_view."/".$tvcs[$c].".".TEMPLATE_EXT)&&strlen($controller)<1){
						$view=$this->config->default_view;
						$controller=$tvcs[$c];
						$c_index=$c;
					}else{
					}
					break;
				case 1:
					if(file_exists(VIEWS_ROOT_DIR.$tvcs[$c])&&strlen($view)<1){
						$view=$tvcs[$c];
						$c_index=$c;
					}elseif(file_exists(VIEWS_ROOT_DIR.$view."/".$tvcs[$c].".".TEMPLATE_EXT)&&strlen($controller)<1){
						$controller=$tvcs[$c];
						$c_index=$c;
					}elseif(file_exists(VIEWS_ROOT_DIR.$this->config->default_view."/".$tvcs[$c].".".TEMPLATE_EXT)&&strlen($controller)<1){
						$view=$this->config->default_view;
						$controller=$tvcs[$c];
						$c_index=$c;
					}
					break;
				case 2:
					if(file_exists(VIEWS_ROOT_DIR.$view."/".$tvcs[$c].".".TEMPLATE_EXT)&&strlen($controller)<1){
						$controller=$tvcs[$c];
						$c_index=$c;
					}
					break;
			}
		}
		
		#If a controller was found, check for query string values.
		#$this->debug("tvcs",$tvcs);
		#$this->debug("c_index",$c_index);
		if(count($tvcs)-1>$c_index){
			$qs=array_slice($tvcs,$c_index+1);
			#$this->debug("qs",$qs);
		}
		
		$theme=(strlen($theme)<1)?$this->config->default_theme:$theme;
		$view=(strlen($view)<1)?$this->config->default_view:$view;
		$controller=(strlen($controller)<1)?$this->config->default_controller:$controller;
		
		if(!file_exists(VIEWS_ROOT_DIR.$view)||!file_exists(VIEWS_ROOT_DIR.$view."/".$controller.".".TEMPLATE_EXT)){
			$theme=$this->config->default_theme;
			$view=$this->config->default_view;
			$controller=$this->config->default_controller;
		}

		$this->setRequestData("GRAVY_QS",$qs);
		
		$this->theme=$theme;
		$this->view=$view;
		$this->controller=$controller;
		$this->error_theme=$this->config->error_theme;
		
		if(!file_exists(THEMES_ROOT_DIR.$this->theme.".".TEMPLATE_EXT)){
			$this->theme=$this->config->default_theme;
			$this->addError($this->ERROR_THEME_NOT_FOUND,"gravy.loadTvcDetails.themeNotFound",array("theme"=>THEMES_ROOT_DIR.$this->theme.".".TEMPLATE_EXT),FATAL_ERROR_ON_404);
		}
		
		if(!file_exists(VIEWS_ROOT_DIR.$this->view)){
			$this->view=$this->config->default_view;
			$this->controller=$this->config->default_controller;
			$this->addError($this->ERROR_VIEW_NOT_FOUND,"gravy.loadTvcDetails.viewNotFound",array("view"=>VIEWS_ROOT_DIR.$this->view),FATAL_ERROR_ON_404);
		}
		
		if(!file_exists(THEMES_ROOT_DIR.$this->error_theme.".".TEMPLATE_EXT)){
			$this->error_theme=$this->theme;
		}
	}

	/*
	** Create a Renderer
	**  Creates a renderer based on the format, then returns it.
	**  @return renderer
	*/
	public function getRenderer($statusCode=null){
		// Add in the various renders here
		#$this->debug("format",$this->format);
		switch($this->format){
			case "xml":
				#$this->debug("YEP- XML");
				if($this->allowXml){
					$renderer=new xmlRenderer($this);
					break;
				}

			case "json":
				#$this->debug("YEP- XML");
				if($this->allowJson){
					$renderer=new jsonRenderer($this);
					break;
				}

			case "ms-download":
                        case "word":
                        case "excel":
                        case "ms":
                                $renderer=new htmlRenderer($this);
                                break;
			
			default:
				$this->format="html";
				$renderer=new htmlRenderer($this);
				break;
		}
		return($renderer);
	}
	
	/*
	** Process the Theme Controller, View Controller and Model Data.
	**  @return void
	*/
	public function process(){
		$themeControllerFile=THEMES_ROOT_DIR."controllers/".$this->theme.".php";
		if(file_exists($themeControllerFile)){
			include($themeControllerFile);
			#call_user_func("controller",$this);
		}
		$viewControllerFile=VIEWS_ROOT_DIR.$this->view."/controllers/".$this->controller.".php";
		if(file_exists($viewControllerFile)){
			#$this->debug("Controller Found",$viewControllerFile);
			include($viewControllerFile);
			#call_user_func("controller",$this);
		}else{
			$this->debug("Controller Not Found",$viewControllerFile);
		}
	}
	
	/*
	** Renderer
	**  Set the headers, creates a renderer based on the format, then renders the document.
	**  @return renderer
	*/
	public function render(){
		$themeController="theme_".$this->theme;
		if(function_exists($themeController)){
			call_user_func($themeController,$this);
		}else{
			#$this->debug("Theme Controller Error",$themeController);
		}
		
		$viewController="action_".$this->controller;
		if(function_exists($viewController)){
			call_user_func($viewController,$this);
		}else{
			$this->debug("View Controller Error",$viewController);
		}
		
		$this->renderer=$this->getRenderer();

		$this->renderer->setHeadersForOutput();
		
		$this->renderer->render();
	}

	/*
	** Render an Error and Exit
	**  Shows a standard error page and exits
	**  @return void
	*/
	public function renderErrorAndExit($statusCode){
		$this->renderer->renderError($statusCode);
		exit();
	}

	/*
	** Add Template Data
	**  Add data to the outgoing template data.
	**  @return void
	*/
	public function addTemplateData($data=NULL){
		if($data!==NULL){
			foreach($data as $key=>$value){
				$this->data[$key]=$value;
			}
		}
	}

	/*
	** Get Template Data
	**  Get the outgoing template data.
	**  @return array
	*/
	public function getTemplateData(){
		$this->recurseTemplateData($this->data);
		return($this->data);
	}

	/*
	** Recurse Template Data
	**  Loop through all the template data and extract the information.
	**  @return array
	*/
	public function recurseTemplateData(&$item){
		//cycle through the template data
		if(is_array($item)){
			#$this->debug("Item is Array");
			foreach($item as $tIndex=>&$tItem){
				if(get_class($tItem)){
					if(method_exists($tItem,"getTemplateData")){
						$tItem=$tItem->getTemplateData();
					}
				}else{
					$this->recurseTemplateData($tItem);
				}
			}
		}
		return($this->data);
	}

	/*
	** Add XML Data
	**  Add data to the outgoing xml data.
	**  @return void
	*/
	public function addXmlData($data=NULL){
		if($data!==NULL){
			foreach($data as $key=>$value){
				$this->xml_data[$key]=$value;
			}
		}
	}

	/*
	** Get XML Data
	**  Get the outgoing XML data.
	**  @return array
	*/
	public function getXmlData(){
		$this->recurseXmlData($this->xml_data);
		return($this->xml_data);
	}

	public function recurseXmlData(&$item){
		//cycle through the template data
		if(is_array($item)){
			#$this->debug("Item is Array");
			foreach($item as $tIndex=>&$tItem){
				if(get_class($tItem)){
					$tItem=$tItem->getTemplateData();
					/*
					if(is_subclass_of($tItem,"model")){
						$this->debug("$tIndex is model subclass");
						#YOU NEED TO DO THIS
						$tItem=$tItem->getTemplateData();
					}else{
						$this->debug("$tIndex is model subclass",$tItem);
						$this->recurseTemplateData($tItem);
					}
					*/
				}else{
					$this->recurseXmlData($tItem);
				}
			}
		}
		return($this->xml_data);
	}

	/*
	** Add JSON Data
	**  Add data to the outgoing JSON data.
	**  @return void
	*/
	public function addJsonData($data=NULL){
		if($data!==NULL){
			foreach($data as $key=>$value){
				$this->json_data[$key]=$value;
			}
		}
	}

	/*
	** Get JSON Data
	**  Get the outgoing JSON data.
	**  @return array
	*/
	public function getJsonData(){
		$this->recurseJsonData($this->json_data);
		return($this->json_data);
	}

	public function recurseJsonData(&$item){
		//cycle through the template data
		if(is_array($item)){
			foreach($item as $tIndex=>&$tItem){
				if(get_class($tItem)){
					$tItem=$tItem->getTemplateData();
				}else{
					$this->recurseJsonData($tItem);
				}
			}
		}
		return($this->json_data);
	}

	/*
	** Set the page's title.
	**  @return bool.
	*/
	public function setPageTitle($title=NULL){
		$this->pageTitle=($title===NULL)?$this->config->default_page_title:$title;
	}

	/*
	** Check whether the user is logged in or not.
	**  @return bool.
	*/
	public function authenticate($loggedIn=false,$redirect=true,$url=NULL){
		if($url===NULL){
			$url=WEB_ROOT;
		}
		if($redirect&&!$loggedIn){
			$this->redirect($this->addToQueryString($url,array("return"=>urlencode($_SERVER['REQUEST_URI']))));
		}
		return($result);
	}

	/*
	** Redirect to a different view.
	**  @return bool.
	*/
	public function redirectToView($view,$controller){
		$result=false;
		if(file_exists(VIEWS_ROOT_DIR."$view/$controller.".TEMPLATE_EXT)){
			$result=true;
			$this->view=$view;
			$this->controller=$controller;
			if(file_exists(VIEWS_ROOT_DIR."$view/controllers/$controller.php")){
				require_once(VIEWS_ROOT_DIR."$view/controllers/$controller.php");
				call_user_func("action_$controller",$this);
			}
		}
		return($result);
	}

	/*
	** Adds a javascript file to be added to the template.
	**  @return true.
	*/
	public function addJavaScriptFile($file){
		$this->js_files[]=$file;
		return(true);
	}

	/*
	** Outputs additional page-specific JS files.
	**  @return true.
	*/
	public function outputJavaScriptFiles(){
		$cache=$this->config->CACHE;
		if(is_string($cache)){
			$cache="?$cache";
		}elseif($cache){
			$cache="?r=".rand(0,10000);
		}else{
			$cache="?r=".rand(0,10000);
		}
		for($c=0;$c<count($this->js_files);$c++){
			print("<script type=\"text/javascript\" src=\"".$this->js_files[$c].$cache."\"></script>".chr(13));
		}
		return(true);
	}

	/*
	** Adds a CSS file to be added to the template.
	**  @return true.
	*/
	public function addCSSFile($file){
		$this->css_files[]=$file;
		return(true);
	}

	/*
	** Outputs additional page-specific CSS files.
	**  @return true.
	*/
	public function outputCSSFiles(){
		$cache=$this->config->CACHE;
		if(is_string($cache)){
			$cache="?$cache";
		}elseif($cache){
			$cache="?r=".rand(0,10000);
		}else{
			$cache="?r=".rand(0,10000);
		}
		for($c=0;$c<count($this->css_files);$c++){
			print("<link rel=\"stylesheet\" type=\"text/css\" media=\"all\" href=\"".$this->css_files[$c].$cache."\" />".chr(13));
		}
		return(true);
	}

	/*
	** Debug the Log Files contents.
	**  @return true.
	*/
	public function logAdmin(){
		if($this->rv["viewLog"]=="please"){
			if($data=$this->logger->read()){
				print("<h1>Gravy Application Log</h1>");
				print("<pre>".stripslashes($data)."</pre>");
				exit();
			}else{
				print("<h1>Gravy Application Log Error</h1>");
				print("<p>Couldnt open the Gravy Application Log for review. File name: ".APP_LOG_FILE."</p>");
				exit();
			}
		}
		if($this->rv["newLog"]=="new"){
			if($data=$this->logger->read()){
				if($this->logger->newLog()){
					print("<h1>New Gravy Application Log Created</h1>");
					print("<pre>".stripslashes($data)."</pre>");
					exit();
				}else{
					print("<h1>New Gravy Application Log Creation Error</h1>");
					print("<p>Couldnt create a new Gravy Application Log...</p>");
					exit();
				}
			}
		}
		return(true);
	}
	
	/*
	** Send a message to all CORE Gravy Administrators (usually developers and very-top level administrators).
	**  @return bool.
	*/
	public function emailAdmin($subject="Message To Administrators",$message="This is an email to all core administrators."){
		$result=false;
		$mail=new phpmailer();
		$mail->Subject=$subject;
		$mail->Body=$message;
		$mail->From=($this->config->serverEmail["email"])?$this->config->serverEmail["email"]:"server@".$_SERVER['HTTP_HOST'];
		$mail->FromName=($this->config->serverEmail["name"])?$this->config->serverEmail["name"]:"server";
		for($c=0;$c<count($this->config->admin);$c++){
			$name=$this->config->admin[$c]["name"];
			$email=$this->config->admin[$c]["email"];
			$mail->AddAddress($email,$name);
			if(!$mail->Send()){
				$this->log("CRITICAL: Could not notify Core Administration Contact (name=$name, email=$email).");
			}
			$mail->ClearAllRecipients();
		}
		return($result);
	}
	
	/*
	** Ouptut PHP INFO.
	**  @return bool.
	*/
	public function phpInfo(){
		if($this->rv["phpinfo"]=="please"){
			phpinfo();
			exit();
		}
	}
}
?>